<!DOCTYPE html>


<html lang="zh-CN">
  

    <head>
      <meta charset="utf-8" />
        
      <meta name="description" content="Java、MySql、Spring、JVM、虚拟机、数据库、Docker、Redis" />
      
      <meta
        name="viewport"
        content="width=device-width, initial-scale=1, maximum-scale=1"
      />
      <title>深入理解JVM（四）垃圾收集器实现细节 |  ACEXS个人博客</title>
  <meta name="generator" content="hexo-theme-ayer">
      
      <link rel="shortcut icon" href="/blog-page/favicon.ico" />
       
<link rel="stylesheet" href="/blog-page/dist/main.css">

      <link
        rel="stylesheet"
        href="https://cdn.jsdelivr.net/gh/Shen-Yu/cdn/css/remixicon.min.css"
      />
      
<link rel="stylesheet" href="/blog-page/css/custom.css">
 
      <script src="https://cdn.jsdelivr.net/npm/pace-js@1.0.2/pace.min.js"></script>
       
 

      <link
        rel="stylesheet"
        href="https://cdn.jsdelivr.net/npm/@sweetalert2/theme-bulma@5.0.1/bulma.min.css"
      />
      <script src="https://cdn.jsdelivr.net/npm/sweetalert2@11.0.19/dist/sweetalert2.min.js"></script>

      <!-- mermaid -->
      
      <style>
        .swal2-styled.swal2-confirm {
          font-size: 1.6rem;
        }
      </style>
    <link rel="alternate" href="/blog-page/atom.xml" title="ACEXS个人博客" type="application/atom+xml">
</head>
  </html>
</html>


<body>
  <div id="app">
    
      
    <main class="content on">
      <section class="outer">
  <article
  id="post-深入理解JVM（四）垃圾收集器实现细节"
  class="article article-type-post"
  itemscope
  itemprop="blogPost"
  data-scroll-reveal
>
  <div class="article-inner">
    
    <header class="article-header">
       
<h1 class="article-title sea-center" style="border-left:0" itemprop="name">
  深入理解JVM（四）垃圾收集器实现细节
</h1>
 

      
    </header>
     
    <div class="article-meta">
      <a href="/blog-page/2021/10/19/7e65be3fe4f8.html" class="article-date">
  <time datetime="2021-10-19T09:24:18.000Z" itemprop="datePublished">2021-10-19</time>
</a> 
  <div class="article-category">
    <a class="article-category-link" href="/blog-page/categories/%E8%AF%BB%E4%B9%A6%E6%88%90%E8%AF%97/">读书成诗</a> / <a class="article-category-link" href="/blog-page/categories/%E8%AF%BB%E4%B9%A6%E6%88%90%E8%AF%97/JVM/">JVM</a>
  </div>
  
<div class="word_count">
    <span class="post-time">
        <span class="post-meta-item-icon">
            <i class="ri-quill-pen-line"></i>
            <span class="post-meta-item-text"> 字数统计:</span>
            <span class="post-count">5.9k</span>
        </span>
    </span>

    <span class="post-time">
        &nbsp; | &nbsp;
        <span class="post-meta-item-icon">
            <i class="ri-book-open-line"></i>
            <span class="post-meta-item-text"> 阅读时长≈</span>
            <span class="post-count">20 分钟</span>
        </span>
    </span>
</div>
 
    </div>
      
    <div class="tocbot"></div>




  
    <div class="article-entry" itemprop="articleBody">
       
  <h1 id="经典垃圾收集器"><a href="#经典垃圾收集器" class="headerlink" title="经典垃圾收集器"></a>经典垃圾收集器</h1><img src="/blog-page/2021/10/19/7e65be3fe4f8/GCM.jpg" class="" width="540" title="This is GCM">

<p><em><strong>tips：图中JDK9红色标记表示，JDK9开始去掉了两种收集器搭配使用。</strong></em></p>
<h2 id="新生代收集器"><a href="#新生代收集器" class="headerlink" title="新生代收集器"></a>新生代收集器</h2><h3 id="Serial收集器"><a href="#Serial收集器" class="headerlink" title="Serial收集器"></a>Serial收集器</h3><h4 id="示意图"><a href="#示意图" class="headerlink" title="示意图"></a>示意图</h4><img src="/blog-page/2021/10/19/7e65be3fe4f8/Serial_SerialOld.jpg" class="" width="540" title="This is Serial_SerialOld">

<h4 id="原理"><a href="#原理" class="headerlink" title="原理"></a>原理</h4><p>使用标记-复制算法实现，单线程垃圾收集器，在进行垃圾收集时，必须暂停其他线程，直到它收集结束。</p>
<h4 id="使用"><a href="#使用" class="headerlink" title="使用"></a>使用</h4><p>是目前HotSpot虚拟机运行在客户端模式下，默认的新生代收集器。</p>
<h3 id="ParNew收集器"><a href="#ParNew收集器" class="headerlink" title="ParNew收集器"></a>ParNew收集器</h3><h4 id="示意图-1"><a href="#示意图-1" class="headerlink" title="示意图"></a>示意图</h4><img src="/blog-page/2021/10/19/7e65be3fe4f8/parnew.jpg" class="" width="540" title="This is parnew">

<h4 id="原理-1"><a href="#原理-1" class="headerlink" title="原理"></a>原理</h4><p>使用标记-复制算法实现，多线程并行垃圾收集器，在运行垃圾收集时，必须暂停其他线程，直到它收集结束。</p>
<h4 id="使用-1"><a href="#使用-1" class="headerlink" title="使用"></a>使用</h4><p>JDK5发布时，推出CMS收集器，CMS无法与JDK1.4.0发布的Parallel Scavenge配合工作，JDK5中的CMS只能与ParNew和Serial收集器中的一个配合使用。JDK9推出了G1收集器，并且去除了ParNew与Serial Old和Serial 与 CMS收集器之间的配合，因此ParNew只能与CMS配合使用，从此ParNew并入CMS，称为第一款HotSpot虚拟机中退出历史舞台的垃圾收集器。</p>
<h4 id="性能"><a href="#性能" class="headerlink" title="性能"></a>性能</h4><p>单核处理器中，ParNew收集器无法保证更高的效率，线程切换可能会造成性能开销。当CPU核心数多时，会有显著性能改善。</p>
<h3 id="Parallel-Scavenge收集器"><a href="#Parallel-Scavenge收集器" class="headerlink" title="Parallel Scavenge收集器"></a>Parallel Scavenge收集器</h3><h4 id="示意图-2"><a href="#示意图-2" class="headerlink" title="示意图"></a>示意图</h4><p>Parallel Scavenge收集器和ParNew非常相似。</p>
<h4 id="原理-2"><a href="#原理-2" class="headerlink" title="原理"></a>原理</h4><p>使用标记-复制算法实现，多线程并行垃圾收集器，运行垃圾收集时，需暂停其他线程，直到它结束。</p>
<h4 id="性能-1"><a href="#性能-1" class="headerlink" title="性能"></a>性能</h4><p>Parallel Scavenge收集器关注的并非回收效率和用户停顿时间，而是程序运行的吞吐量，尽可能短暂的停顿用户线程，或尽可能保证用户线程时空间分配的效率。以此达到比较高的吞吐量。</p>
<h2 id="老年代收集器"><a href="#老年代收集器" class="headerlink" title="老年代收集器"></a>老年代收集器</h2><h3 id="CMS（Concurrent-Mark-Sweep）收集器"><a href="#CMS（Concurrent-Mark-Sweep）收集器" class="headerlink" title="CMS（Concurrent Mark Sweep）收集器"></a>CMS（Concurrent Mark Sweep）收集器</h3><h4 id="示意图-3"><a href="#示意图-3" class="headerlink" title="示意图"></a>示意图</h4><img src="/blog-page/2021/10/19/7e65be3fe4f8/CMS.jpg" class="" width="540" title="This is CMS">

<h4 id="原理-3"><a href="#原理-3" class="headerlink" title="原理"></a>原理</h4><p>使用标记-清除算法实现，多线程并发执行的垃圾收集器，仅在初始标记和重新标记阶段需要暂停其他线程，进行垃圾收集，其他阶段可以与用户线程并行。CMS进行垃圾收集时主要经过以下阶段：</p>
<ul>
<li>初始标记：标记GC Roots能够直接关联到的对象。</li>
<li>并发标记：从GC Roots直接关联对象开始遍历整个对象图。</li>
<li>重新标记（增量更新算法实现）：修正并发标记阶段，因为用户线程继续运行而导致标记产生变动的那部分对象的标记记录。</li>
<li>并发清除：清理掉标记判断的已经死亡的对象。</li>
</ul>
<h4 id="使用-2"><a href="#使用-2" class="headerlink" title="使用"></a>使用</h4><p>JDK9版本中已经没有能够和CMS配合使用的新生代垃圾收集器，因此在JDK9中，CMS被标记为不推荐使用，JDK14版本中移出了此收集器。？？</p>
<h3 id="Serial-Old-收集器"><a href="#Serial-Old-收集器" class="headerlink" title="Serial Old 收集器"></a>Serial Old 收集器</h3><h4 id="示意图-4"><a href="#示意图-4" class="headerlink" title="示意图"></a>示意图</h4><img src="/blog-page/2021/10/19/7e65be3fe4f8/Serial_SerialOld.jpg" class="" width="540" title="This is Serial_SerialOld">

<h4 id="原理-4"><a href="#原理-4" class="headerlink" title="原理"></a>原理</h4><p>使用标记-整理算法实现，单线程垃圾收集器，运行垃圾收集时，需暂停其他线程，直到它结束。</p>
<h4 id="使用-3"><a href="#使用-3" class="headerlink" title="使用"></a>使用</h4><ul>
<li>HotSpot虚拟机客户端模式下，默认使用此收集器进行老年代回收。</li>
<li>JDK5以及之前版本中，与Parallel Scavenge（并非Parallel Scavenge 收集器默认调用Serial Old收集器，而是使用自身PS MarkSweep收集器进行老年代回收，因为PS Sweep实现与Serial Old 基本上完全一致）收集器搭配使用。</li>
<li>作为CMS收集器的收集失败的的后被预案，在并发收集发生Concurrent Mode Failure时使用。</li>
</ul>
<h3 id="Parallel-Old-收集器"><a href="#Parallel-Old-收集器" class="headerlink" title="Parallel Old 收集器"></a>Parallel Old 收集器</h3><h4 id="示意图-5"><a href="#示意图-5" class="headerlink" title="示意图"></a>示意图</h4><img src="/blog-page/2021/10/19/7e65be3fe4f8/parallel_scavenge_parallel_old.jpg" class="" width="540" title="This is parallel_scavenge_parallel_old">

<h4 id="原理-5"><a href="#原理-5" class="headerlink" title="原理"></a>原理</h4><p>使用标记-整理算法实现的收集器，是Parallel Scavenge收集器的老年代版本，多线程并发进行老年代垃圾收集，当进行垃圾收集时，需要暂停其他线程，直到它结束。</p>
<h4 id="使用-4"><a href="#使用-4" class="headerlink" title="使用"></a>使用</h4><p>JDK6之前，没有提供此收集器，因此Parallel Scavenge收集器只能与Serial Old进行搭配使用，Serial Old的单线程特点，老年代大对象或难以消除的对象收集时，显得略有些笨重，与其他多线程收集器比起来，效率偏低，甚至在老年代内存空间很大且硬件规格比较高时，使得这两个收集器搭配起来吞吐量比其他组合降低。有了Parallel Old与Parallel Scavenge搭配，才使得这两款收集器在整体收集过程中发挥出色表现。</p>
<h1 id="最新垃圾收集器"><a href="#最新垃圾收集器" class="headerlink" title="最新垃圾收集器"></a>最新垃圾收集器</h1><h2 id="全局收集器"><a href="#全局收集器" class="headerlink" title="全局收集器"></a>全局收集器</h2><h3 id="G1-（Grabge-First）收集器"><a href="#G1-（Grabge-First）收集器" class="headerlink" title="G1 （Grabge First）收集器"></a>G1 （Grabge First）收集器</h3><h4 id="示意图-6"><a href="#示意图-6" class="headerlink" title="示意图"></a>示意图</h4><img src="/blog-page/2021/10/19/7e65be3fe4f8/parallel_scavenge_parallel_old.jpg" class="" width="540" title="This is parallel_scavenge_parallel_old">

<h4 id="原理（停顿时间模型）"><a href="#原理（停顿时间模型）" class="headerlink" title="原理（停顿时间模型）"></a>原理（停顿时间模型）</h4><p><strong>分代思想：</strong></p>
<ul>
<li><p>G1跳出原始分代理论，它可以面向堆内存任何部分来组成回收集进行回收，衡量标准不再是哪个分代，而是哪块内存存放的垃圾数量最多，回收收益最大，实行Mixed GC模式。</p>
</li>
<li><p>G1开创的基于Region的堆内存布局，是实现分块回收的关键。虽然G1仍遵循分代收集理论设计，但G1不再坚持固定大小以及固定数量的分代区域划分，而是把连续的Java堆划分为多个大小相等的独立区域（Region）每一个Region都可以根据需要扮演新生代的Eden空间、Survivor空间或者老年代空间。</p>
</li>
<li><p>Region还有一类特殊的Humongous区域，专门用来存储大对象，G1人为只要大小超过一个Region容量的一半的对象，即可判定为大对象。每个Region大可以通过参数控制，取值范围1M~32M，且为2的N次幂。对于哪些超过整个Region容量的超级大对象，将会被存放在N个连续的Humongous Region中。</p>
</li>
<li><p>Region作为单次回收的最小单元，每次回收内存空间都是Region的整数倍，可以避免在整个Java堆中进行全区域的垃圾收集。具体处理思路是让G1收集器跟踪各个Region里面的垃圾堆积的“价值”大小，然后在后台维护一个优先级列表，每次根据用户设定的允许收集停顿时间，优先处理回收价值收益最大的那些Region。</p>
</li>
</ul>
<p><strong>回收阶段：</strong></p>
<ul>
<li><strong>初始标记：</strong>标记GC Roots能直接关联到的对象，并且修改TAMS指针的值，让下一阶段用户线程并发运行时，正确地在可用的Region中分配新对象。这个阶段需要停顿线程。</li>
<li><strong>并发标记：</strong>从GC Roots开始对队中对象进行可达性分析，递归扫描堆中对象图。对象图扫描完成后，还要重新处理SATB记录下的在并发时有引用变动的对象。</li>
<li><strong>最终标记：</strong>堆用户线程做短暂停顿，用于并发阶段结束后仍遗留下来地最后那少量SATB记录。</li>
<li><strong>筛选回收：</strong>更新Region统计数据对各个Region回收价值和成本进行排序，根据用户所期望地停顿时间制定回收计划。</li>
</ul>
<p><strong>解决问题：</strong></p>
<ul>
<li><p><strong>跨Region引用问题：</strong></p>
<p><em>相比于跨代引用实用记忆集和卡表来维护，跨Region的引用，存储起来更复杂些，每个Region都维护自己的记忆集，这些记忆集记录下别的Region指向自己的指针，并标记这些指针分别在那些卡页范围之内。G1的记忆集在存储结构的本质是一种哈希表，Key是别的Region的起始地址，Value是一个集合，里面存储的元素是卡表的索引号。这种“双向卡表”结构比原来的卡表实现起来更复杂，同时由于Region数量远超其他传统垃圾收集器的分代数量，所以会有更高内存负担。根据经验G1收集器至少要耗费大约相当于Java堆容量10%~20%的额外内存来维持收集器工作。</em></p>
</li>
<li><p><strong>并发时排除用户线程干扰：</strong></p>
<p>（1）、用户线程并发运行，改变对象引用关系时，使用<a href="https://acexxs.gitee.io/blog-page/2021/10/19/1c38fbd90de1.html">原始快照算法</a>保证原本对象图标记的正确性。</p>
<p>（2）、用户线程并发运行，创建新对象进行内存分配时，G1为每个Region设计了两个名为<strong>TAMS</strong>的指针，把Region中的一部分空间划分出来，用于并发回收过程中的内存分配，并发回收时，新分配的对象地址都要必须在这两个指针位置以上。G1收集器默认在这个地址以上的对象都是被隐式标记过的，默认它们是存活的。</p>
</li>
<li><p><strong>可靠的时间停顿模型：</strong></p>
<p>用户可以通过参数设置垃圾收集发生之前的期望值，G1收集器的停顿预测模型是以衰减平均值为理论基础实现的。在垃圾收集过程中，G1收集器会记录每个Region的回收耗时、每个Region记忆集里的脏卡数量等各个可测量的步骤花费成本，并分析得出平均值、标准偏差、置信度等统计信息。实用衰减平均值的原因是因为它比较能够代表最近的平均程度，Region统计状态越新，越能决定其回收价值。</p>
</li>
</ul>
<h4 id="使用-5"><a href="#使用-5" class="headerlink" title="使用"></a>使用</h4><ul>
<li><p>面向服务端应用的垃圾收集器。</p>
</li>
<li><p>JDK6 Update 14开始就有Early Access版本的G1收集器供开发人员实验和试用。</p>
</li>
<li><p>JDK确立项目目标，JDK7 RoadMap里面，G1收集器被视作JDK7中HotSpot虚拟机的一项重要进化特征。</p>
</li>
<li><p>JDK7 Update 4，Oracle移除实验标识，开始商用。</p>
</li>
<li><p>JDK 8 Update 40的时候，G1 提供并发的类型卸载的支持，补全了计划功能的最后一块拼图。</p>
</li>
</ul>
<h2 id="低延迟垃圾收集器"><a href="#低延迟垃圾收集器" class="headerlink" title="低延迟垃圾收集器"></a>低延迟垃圾收集器</h2><h3 id="Shenandoah收集器"><a href="#Shenandoah收集器" class="headerlink" title="Shenandoah收集器"></a>Shenandoah收集器</h3><h4 id="原理-6"><a href="#原理-6" class="headerlink" title="原理"></a>原理</h4><p><strong>区域分布：</strong></p>
<ul>
<li>Shenandoah收集器可以看作是G1收集器的升级版本，在实现思路上，很多阶段都和G1保持高度一致，甚至共享一部分实现代码。</li>
<li>Shenandoah默认不使用分代收集。</li>
<li>Shenandoah不使用G1模式的记忆集，使用“连接矩阵”的全局数据结构记录跨Region引用问题。可以看作是一个二维数组，对存在跨代引用的区域进行标记并记录在连接矩阵中。</li>
</ul>
<p><strong>回收步骤：</strong></p>
<ul>
<li><strong>初始标记：</strong>标记与GC Roots直接关联的对象，需要暂停用户线程，停顿时间与堆大小无关。</li>
<li><strong>并发标记：</strong>遍历对象图，标记出全部可达对象，与用户线程一起并发。</li>
<li><strong>最终标记：</strong>处理剩余的SATB扫描，在这个阶段统计出回收价值最高的Region。会有一小段时间暂停用户线程。</li>
<li><strong>并发清理：</strong>清理整个区域内连一个存活对象都没有找到的Region。</li>
<li><strong>并发回收：</strong>把会收集里面的存货对象复制一份到其他未被使用的Region中，此处使用读屏障和转发指针来解决。</li>
<li><strong>初始引用更新：</strong>并发回收阶段复制对象结束后，把堆中所有指向旧对象的引用修正到复制后的新地址，此过程不进行什么具体操作，目的是为了建立一个集合点，确保所有线程完成了分配给它们的对象赋值任务，此过程会短暂暂停用户线程，时间很短。</li>
<li><strong>并发引用更新：</strong>开始并发的进行引用更新操作，此阶段时间长短取决于内存中涉及的引用数量多少，它不需要按照对象图遍历，只需要线性的搜索出引用类型完成更新。</li>
<li><strong>最终引用更新：</strong>修正存在于GC Roots 中的引用，此过程会出现短暂暂停用户线程，暂停时间和GC Roots大小相关。</li>
<li><strong>并发清理：</strong>经过上述步骤后，整个回收集中的所有Region都已不再有存活对象，再调用以此并发清理过程，回收这些区域。</li>
</ul>
<p><strong>解决问题：</strong></p>
<ul>
<li><p><strong>保证暂停用户线程短暂且可控</strong></p>
<p>Shenandoah支持并发的整理算法，G1回收阶段虽然是并行的，但不能与用户线程并发。</p>
</li>
<li><p><strong>转发指针保证对象访问的正确性</strong></p>
<p>类似于通过句柄转发对象访问，只不过此处的转发指针存储在对象头中（原始的转发指针使用保护陷阱方式，进入预设异常处理器后，转发到新对象的地址上，这种方式如果没有操作系统层面支持，需要频繁进行用户态和核心态的切换，代价高昂）间接的进行对象访问，无论转发指针指向的是对象本身还是复制后的新对象地址，其内容应该是一致的。</p>
</li>
<li><p><strong>如何保证转发指针访问新旧对象的一致性</strong></p>
<p>确保收集线程复制新的对象副本和收集线程更新转发指针引用值为新副本的地址这两个操作的原子性，这两个操作之间不能插入用户线程对原始对象的修改操作。Shenandoah收集器通过比骄傲并交换（CAS）操作来保证此操作。</p>
</li>
<li><p><strong>执行频率控制</strong></p>
<p>Shenandoah收集器分别使用了读、写屏障来保证转发指针功能的正常，写屏障除了维护记忆集等操作外，也加入了转发功能，但相对写屏障，读屏障更加频繁，所以要求读屏障中不允许加入任何重量级操作。</p>
</li>
</ul>
<h4 id="使用-6"><a href="#使用-6" class="headerlink" title="使用"></a>使用</h4><p>Shenandoah目前没有加入Oracle JDK版本中，只能在OpenJDK中见到它的身影。</p>
<h4 id="性能-2"><a href="#性能-2" class="headerlink" title="性能"></a>性能</h4><p>Shenandoah收集器目前还未完成预设的只停顿10ms的目标，但是对于用户现成的停顿时间已经有了很大改观，但总运行时间也相应增加了，程序吞吐量相对来说有所降低。</p>
<h3 id="ZGC收集器"><a href="#ZGC收集器" class="headerlink" title="ZGC收集器"></a>ZGC收集器</h3><h4 id="原理-7"><a href="#原理-7" class="headerlink" title="原理"></a>原理</h4><p>ZGC更类似于PGC和C4的同胞兄弟。</p>
<p><strong>内存布局：</strong></p>
<ul>
<li>在ZGC官方文档中，Region被称为Page或ZPage。</li>
<li>ZGC中的Region是动态的，可动态创建和销毁。</li>
<li>小型Region：容量固定为2MB，用于放置小于256KB的对象。</li>
<li>中型Region：容量固定为32MB，用于存储大于或等于256KB但小于4MB的对象。</li>
<li>大型Region：容量不固定，可动态变化，但必须为2MB的整数倍，用于放置4MB或以上的大对象，每个大型Region中只存放一个大对象。实际容量可能小于中型Region，可低至4MB。大型Region在ZGC中是不会被重分配的，因为复制一个大对象代价高昂。</li>
</ul>
<p><strong>染色指针：</strong></p>
<ul>
<li>将少量额外信息存储在指针上的技术</li>
<li>操作系统中，位数和可访问内存之间存在某些关系，如64位系统，可访问内存是2的64次幂，为16EB。但考虑掉成本、需求、性能等问题，实际用不到这么大内存，如AMD64架构中，只支持到52（4PB）位的地址总线和48（256TB）位的虚拟地址空间。甚至不同操作系统一侧也会施加约束（如64位Linux系统分别支持47位128TB）的进程虚拟地址空间和46位（64TB）的物理地址空间，64位的Windows系统甚至只支持44位（16TB）的物理地址空间。</li>
<li>如果基于Linux平台，Linux下的64位指针的高18位不能用来寻址，但剩余的46位所能支持的64TB内存，仍能满足大部分需求。ZGC使用这46位的高4位进行标志信息，通过这些标记位，虚拟机可以直接从指针中看出其引用对象的<a href="https://acexxs.gitee.io/blog-page/2021/10/19/1c38fbd90de1.html">三色标记</a>状态、是否进入重分配集、是否只能通过finalize()方法才能被访问到等信息。</li>
<li>基于上述特点，也有一些局限性，ZGC占用了高4位，剩余42位内存大小为4TB，所以ZGC可管理内存不能超过4TB。不能支持32位平台。不能支持压缩指针等。</li>
<li>染色指针，使对象引用变化更具实时性，垃圾收集也更具实时性。空间占用上，理论上只要还有一个空闲的Region，就能够完成垃圾收集，在某些所有对象都存活的极端情况下Shenandoah收集器需要1:1的空闲空间，去完成复制操作。</li>
<li>染色指针，减少了垃圾收集过程中的屏障的使用数量。</li>
<li>染色指针可以作为一种可拓展的数据结构，用来记录更多与对象标记、重定位相关的数据结构，以便日后进一步提高性能。（如利用起高18位，腾退原来高4位）</li>
</ul>
<p><strong>多重内存映射：</strong></p>
<img src="/blog-page/2021/10/19/7e65be3fe4f8/vm_remapped.png" class="" width="540" title="This is vm_remapped">

<p><em><strong>个人理解：</strong></em></p>
<ul>
<li>多个不同虚拟机内存地址映射到同一个物理内存地址上，由于染色指针直接修改的是指针高4位信息，相当于改变了原始指针的地址，使用多重内存映射一方面可以保证寻址正常；</li>
<li>染色指针的本质是在引用地址上加上三色标记，无论是垃圾收集过程，还是用户线程改变了引用关系，都需要修改引用信息，因此，需要做到无论标志位如何变化，指向的对象永远是同一个。</li>
<li>多重内存映射，是染色指针可以发挥作用的前提条件，因为多重映射，可以让对象在物理地址保持不变的前提下，引用关系展现为多种不同形态。</li>
</ul>
<p><strong>回收步骤：</strong></p>
<ul>
<li><p><strong>并发标记：</strong>遍历对象图进行可达性分析，前后也需要经过类似初始标记、最终标记的短暂停顿，与Shenandoah的区别在于，这些标记过程在指针上进行。更新染色指针的Marked0、Marked1标志位。</p>
</li>
<li><p><strong>并发预备重分配：</strong>查询统计出本次回收过程要清理哪些Region，将这些Region组成重分配集。与Shenandoah回收集的区别在于，ZGC的重分配集会每次都扫描所有Region，用时间换取计算资源和维护成本。所以ZGC每次回收都是针对全堆，每次都是Full GC。同时此过程还会完成类型卸载以及弱引用处理。</p>
</li>
<li><p><strong>并发重分配：</strong>将重分配集中的存活对象复制到新的Region上，并为重分配集的每个Region维护一个转发表，记录从旧对象到新对象的转向关系。如果用户线程访问对象在重分配集中，通过读屏障，可以根据指针上的标记得知对象，并根据转发记录表将访问转发到新复制的对象，并同时修正该更新引用的值，使其直接指向新duixiang (指针的”自愈”能力)。重分配集中的某个Region的存活对象都复制完成后，这个Region就可以释放掉，用于新对象的分配。</p>
</li>
<li><p><strong>并发重映射：</strong>修正整个Java堆中指向重分配集中旧对象的所有引用，但ZGC的并发重映射阶段不是一个必须迫切需要完成的任务，因为存在指针自愈，至多是多经历一次转发，就可以修正掉，所以ZGC将这一步，与下次垃圾收集开始的时候，遍历对象图结合到一起去完成。并发重映射还有另一个作用，就是释放转发表。</p>
</li>
</ul>
<h1 id="对比分析"><a href="#对比分析" class="headerlink" title="对比分析"></a>对比分析</h1><h2 id="垃圾收集器选择"><a href="#垃圾收集器选择" class="headerlink" title="垃圾收集器选择"></a>垃圾收集器选择</h2><p><strong>衡量垃圾收集器标准：</strong>内存占用、吞吐量、延迟（三者构成不可能三角，目前没有一款垃圾收集器可以同时满足以上三个条件，只是在这三者之间进行权衡，选择合适的实现方案实现垃圾收集器）。</p>
<p><strong>垃圾收集器选择标准：</strong></p>
<p>（1）、应用特点：数据分析计算类的任务多，需要关注吞吐量；SLA应用主要关注延迟；客户端应用或嵌入式应用，主要关注内存占用。</p>
<p>（2）、运行应用的基础设施：硬件规格、系统架构、处理器数量、分配内存大小、操作系统等。</p>
<p>（3）、使用JDK：JDK发行商、JDK版本号等。</p>
<h2 id="表格分析"><a href="#表格分析" class="headerlink" title="表格分析"></a>表格分析</h2><table>
<thead>
<tr>
<th align="left">收集器</th>
<th align="left">回收区域</th>
<th align="left">实现算法</th>
<th align="left">优势</th>
<th align="left">缺陷</th>
<th align="left">Full GC</th>
</tr>
</thead>
<tbody><tr>
<td align="left">Serial</td>
<td align="left">新生代</td>
<td align="left">标记-复制算法</td>
<td align="left">1、实现简单<br />2、内存占用低<br />3、资源消耗少<br />4、没有空间碎片</td>
<td align="left">1、用户线程停顿时间长</td>
<td align="left">无</td>
</tr>
<tr>
<td align="left">ParNew</td>
<td align="left">新生代</td>
<td align="left">标记-复制算法</td>
<td align="left">1、多线程进行垃圾收集，特定场景下效率更高<br />2、没有空间碎片</td>
<td align="left">1、资源受限场景下，线程交互可能会带来额外开销，未必会比单线程收集器效率更高<br />2、需要暂停用户线程，直至垃圾收集完成</td>
<td align="left">无</td>
</tr>
<tr>
<td align="left">Parallel Scavenge</td>
<td align="left">新生代</td>
<td align="left">标记-复制算法</td>
<td align="left">1、吞吐量高<br />2、多线程并发回收<br />3、没有空间碎片</td>
<td align="left">1、用户线程暂停时间可能会比其他收集器更长<br />2、资源受限场景下，线程交互可能会带来额外开销，未必会比单线程收集器效率更高</td>
<td align="left">无</td>
</tr>
<tr>
<td align="left">Serial Old</td>
<td align="left">老年代</td>
<td align="left">标记-整理算法</td>
<td align="left">1、资源占用低<br />2、内存消耗低<br />3、实现简单<br />4、没有空间碎片</td>
<td align="left">1、用户线程暂停时间长</td>
<td align="left">无</td>
</tr>
<tr>
<td align="left">Parallel Old</td>
<td align="left">老年代</td>
<td align="left">标记-整理算法</td>
<td align="left">1、吞吐量高<br />2、没有空间碎片</td>
<td align="left">1、用户线程暂停时间可能会比其他收集器更长<br />2、资源受限场景下，线程交互可能会带来额外开销，未必会比单线程收集器效率更高</td>
<td align="left">无</td>
</tr>
<tr>
<td align="left">CMS</td>
<td align="left">老年代</td>
<td align="left">标记-清除算法</td>
<td align="left">1、并发收集<br />2、低停顿<br /></td>
<td align="left">1、对资源敏感（默认启动回收线程数是：(处理器核心数 + 3 )/4)，资源占用较高。<br />2、处理器核心数低时，用户线程执行效率变低（抢占了用户线程运行的资源）<br />3、无法处理浮动垃圾<br />4、容易产生空间碎片</td>
<td align="left">1、分配速率高于回收速率<br />2、出现Concurrent Mode Failure异常，通过Serial Old触发Full GC</td>
</tr>
<tr>
<td align="left">G1</td>
<td align="left">整堆</td>
<td align="left">标记-复制算法</td>
<td align="left">1、全局垃圾收集<br />2、用户线程停顿时间可控<br />3、吞吐量和低延迟共同考量<br />4、按收益回收，加快回收效率，降低Full GC 频率<br />5、没有空间碎片</td>
<td align="left">1、内存占用更高<br />2、额外负载更高</td>
<td align="left">分配速率大于回收速率时，触发Full GC</td>
</tr>
<tr>
<td align="left">Shenandoah</td>
<td align="left">整堆</td>
<td align="left">可并发的标记-整理算法</td>
<td align="left">1、低延迟<br />2、回收效率高<br />3、性能表现优于前几个收集器</td>
<td align="left">1、资源占用高<br />2、用户线程执行效率低（读、写屏障导致）<br />3、吞吐量方面表现不佳</td>
<td align="left">分配速率大于回收速率时，触发并发Full GC</td>
</tr>
<tr>
<td align="left">ZGC</td>
<td align="left">整堆</td>
<td align="left">可并发的标记-整理算法</td>
<td align="left">1、低延迟<br />2、回收效率高<br />3、用户线程停顿时间短且可控<br />4、较少的读、写屏障保证用户线程执行效率<br />5、指针自愈，可以不影响用户线程访问对象<br />6、支持NUMA-Aware的内存分配，多核处理器中内存分配效率高</td>
<td align="left">1、染色指针，使ZGC可管理内存空间变小<br />2、实现原理复杂</td>
<td align="left">每次回收都是Full GC</td>
</tr>
</tbody></table>
 
      <!-- reward -->
      
      <div id="reword-out">
        <div id="reward-btn">
          打赏
        </div>
      </div>
      
    </div>
    

    <!-- copyright -->
    
    <div class="declare">
      <ul class="post-copyright">
        <li>
          <i class="ri-copyright-line"></i>
          <strong>版权声明： </strong>
          
          本博客所有文章除特别声明外，著作权归作者所有。转载请注明出处！
          
        </li>
      </ul>
    </div>
    
    <footer class="article-footer">
       
<div class="share-btn">
      <span class="share-sns share-outer">
        <i class="ri-share-forward-line"></i>
        分享
      </span>
      <div class="share-wrap">
        <i class="arrow"></i>
        <div class="share-icons">
          
          <a class="weibo share-sns" href="javascript:;" data-type="weibo">
            <i class="ri-weibo-fill"></i>
          </a>
          <a class="weixin share-sns wxFab" href="javascript:;" data-type="weixin">
            <i class="ri-wechat-fill"></i>
          </a>
          <a class="qq share-sns" href="javascript:;" data-type="qq">
            <i class="ri-qq-fill"></i>
          </a>
          <a class="douban share-sns" href="javascript:;" data-type="douban">
            <i class="ri-douban-line"></i>
          </a>
          <!-- <a class="qzone share-sns" href="javascript:;" data-type="qzone">
            <i class="icon icon-qzone"></i>
          </a> -->
          
          <a class="facebook share-sns" href="javascript:;" data-type="facebook">
            <i class="ri-facebook-circle-fill"></i>
          </a>
          <a class="twitter share-sns" href="javascript:;" data-type="twitter">
            <i class="ri-twitter-fill"></i>
          </a>
          <a class="google share-sns" href="javascript:;" data-type="google">
            <i class="ri-google-fill"></i>
          </a>
        </div>
      </div>
</div>

<div class="wx-share-modal">
    <a class="modal-close" href="javascript:;"><i class="ri-close-circle-line"></i></a>
    <p>扫一扫，分享到微信</p>
    <div class="wx-qrcode">
      <img src="//api.qrserver.com/v1/create-qr-code/?size=150x150&data=http://acexxs.gitee.io/blog-page/2021/10/19/7e65be3fe4f8.html" alt="微信分享二维码">
    </div>
</div>

<div id="share-mask"></div>  
  <ul class="article-tag-list" itemprop="keywords"><li class="article-tag-list-item"><a class="article-tag-list-link" href="/blog-page/tags/GC/" rel="tag">GC</a></li><li class="article-tag-list-item"><a class="article-tag-list-link" href="/blog-page/tags/JVM/" rel="tag">JVM</a></li><li class="article-tag-list-item"><a class="article-tag-list-link" href="/blog-page/tags/Java/" rel="tag">Java</a></li></ul>

    </footer>
  </div>

   
  <nav class="article-nav">
    
      <a href="/blog-page/2021/10/19/788230c0de2b.html" class="article-nav-link">
        <strong class="article-nav-caption">上一篇</strong>
        <div class="article-nav-title">
          
            深入理解JVM（五）垃圾收集日志及实用虚拟机工具
          
        </div>
      </a>
    
    
      <a href="/blog-page/2021/10/19/2fe2b377cca6.html" class="article-nav-link">
        <strong class="article-nav-caption">下一篇</strong>
        <div class="article-nav-title">深入理解JVM（三）垃圾收集基础原理</div>
      </a>
    
  </nav>

   
<!-- valine评论 -->
<div id="vcomments-box">
  <div id="vcomments"></div>
</div>
<script src="//cdn1.lncld.net/static/js/3.0.4/av-min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/valine@1.4.14/dist/Valine.min.js"></script>
<script>
  new Valine({
    el: "#vcomments",
    app_id: "4Gx0dzptNxW0Nb4xekYQLQF1-gzGzoHsz",
    app_key: "nvoM2TGGO7QpNlr3vAy3DQyg",
    path: window.location.pathname,
    avatar: "monsterid",
    placeholder: "欢迎一起探讨交流~",
    recordIP: true,
  });
  const infoEle = document.querySelector("#vcomments .info");
  if (infoEle && infoEle.childNodes && infoEle.childNodes.length > 0) {
    infoEle.childNodes.forEach(function (item) {
      item.parentNode.removeChild(item);
    });
  }
</script>
<style>
  #vcomments-box {
    padding: 5px 30px;
  }

  @media screen and (max-width: 800px) {
    #vcomments-box {
      padding: 5px 0px;
    }
  }

  #vcomments-box #vcomments {
    background-color: #fff;
  }

  .v .vlist .vcard .vh {
    padding-right: 20px;
  }

  .v .vlist .vcard {
    padding-left: 10px;
  }
</style>

 
   
     
</article>

</section>
      <footer class="footer">
  <div class="outer">
    <ul>
      <li>
        Copyrights &copy;
        2021
        <i class="ri-heart-fill heart_icon"></i> ACEXS
      </li>
    </ul>
    <ul>
      <li>
        
      </li>
    </ul>
    <ul>
      <li>
        
        
        <span>
  <span><i class="ri-user-3-fill"></i>访问人数:<span id="busuanzi_value_site_uv"></span></span>
  <span class="division">|</span>
  <span><i class="ri-eye-fill"></i>浏览次数:<span id="busuanzi_value_page_pv"></span></span>
</span>
        
      </li>
    </ul>
    <ul>
      
    </ul>
    <ul>
      
    </ul>
    <ul>
      <li>
        <!-- cnzz统计 -->
        
        <script type="text/javascript" src='https://s9.cnzz.com/z_stat.php?id=1278069914&amp;web_id=1278069914'></script>
        
      </li>
    </ul>
  </div>
</footer>    
    </main>
    <div class="float_btns">
      <div class="totop" id="totop">
  <i class="ri-arrow-up-line"></i>
</div>

<div class="todark" id="todark">
  <i class="ri-moon-line"></i>
</div>

    </div>
    <aside class="sidebar on">
      <button class="navbar-toggle"></button>
<nav class="navbar">
  
  <div class="logo">
    <a href="/blog-page/"><img src="/blog-page/images/ayer-side.svg" alt="ACEXS个人博客"></a>
  </div>
  
  <ul class="nav nav-main">
    
    <li class="nav-item">
      <a class="nav-item-link" href="/blog-page/">主页</a>
    </li>
    
    <li class="nav-item">
      <a class="nav-item-link" href="/blog-page/archives">归档</a>
    </li>
    
    <li class="nav-item">
      <a class="nav-item-link" href="/blog-page/categories">分类</a>
    </li>
    
    <li class="nav-item">
      <a class="nav-item-link" href="/blog-page/tags">标签</a>
    </li>
    
    <li class="nav-item">
      <a class="nav-item-link" href="/blog-page/friends">友链</a>
    </li>
    
    <li class="nav-item">
      <a class="nav-item-link" href="/blog-page/about">关于我</a>
    </li>
    
  </ul>
</nav>
<nav class="navbar navbar-bottom">
  <ul class="nav">
    <li class="nav-item">
      
      <a class="nav-item-link nav-item-search"  title="搜索">
        <i class="ri-search-line"></i>
      </a>
      
      
      <a class="nav-item-link" target="_blank" href="/blog-page/atom.xml" title="RSS Feed">
        <i class="ri-rss-line"></i>
      </a>
      
    </li>
  </ul>
</nav>
<div class="search-form-wrap">
  <div class="local-search local-search-plugin">
  <input type="search" id="local-search-input" class="local-search-input" placeholder="Search...">
  <div id="local-search-result" class="local-search-result"></div>
</div>
</div>
    </aside>
    <div id="mask"></div>

<!-- #reward -->
<div id="reward">
  <span class="close"><i class="ri-close-line"></i></span>
  <p class="reward-p"><i class="ri-cup-line"></i>表扬一下吧~</p>
  <div class="reward-box">
    
    <div class="reward-item">
      <img class="reward-img" src="/blog-page/images/alipay.png">
      <span class="reward-type">支付宝</span>
    </div>
    
    
    <div class="reward-item">
      <img class="reward-img" src="/blog-page/images/wechat.png">
      <span class="reward-type">微信</span>
    </div>
    
  </div>
</div>
    
<script src="/blog-page/js/jquery-3.6.0.min.js"></script>
 
<script src="/blog-page/js/lazyload.min.js"></script>

<!-- Tocbot -->
 
<script src="/blog-page/js/tocbot.min.js"></script>

<script>
  tocbot.init({
    tocSelector: ".tocbot",
    contentSelector: ".article-entry",
    headingSelector: "h1, h2, h3, h4, h5, h6",
    hasInnerContainers: true,
    scrollSmooth: true,
    scrollContainer: "main",
    positionFixedSelector: ".tocbot",
    positionFixedClass: "is-position-fixed",
    fixedSidebarOffset: "auto",
  });
</script>

<script src="https://cdn.jsdelivr.net/npm/jquery-modal@0.9.2/jquery.modal.min.js"></script>
<link
  rel="stylesheet"
  href="https://cdn.jsdelivr.net/npm/jquery-modal@0.9.2/jquery.modal.min.css"
/>
<script src="https://cdn.jsdelivr.net/npm/justifiedGallery@3.7.0/dist/js/jquery.justifiedGallery.min.js"></script>

<script src="/blog-page/dist/main.js"></script>

<!-- ImageViewer -->
 <!-- Root element of PhotoSwipe. Must have class pswp. -->
<div class="pswp" tabindex="-1" role="dialog" aria-hidden="true">

    <!-- Background of PhotoSwipe. 
         It's a separate element as animating opacity is faster than rgba(). -->
    <div class="pswp__bg"></div>

    <!-- Slides wrapper with overflow:hidden. -->
    <div class="pswp__scroll-wrap">

        <!-- Container that holds slides. 
            PhotoSwipe keeps only 3 of them in the DOM to save memory.
            Don't modify these 3 pswp__item elements, data is added later on. -->
        <div class="pswp__container">
            <div class="pswp__item"></div>
            <div class="pswp__item"></div>
            <div class="pswp__item"></div>
        </div>

        <!-- Default (PhotoSwipeUI_Default) interface on top of sliding area. Can be changed. -->
        <div class="pswp__ui pswp__ui--hidden">

            <div class="pswp__top-bar">

                <!--  Controls are self-explanatory. Order can be changed. -->

                <div class="pswp__counter"></div>

                <button class="pswp__button pswp__button--close" title="Close (Esc)"></button>

                <button class="pswp__button pswp__button--share" style="display:none" title="Share"></button>

                <button class="pswp__button pswp__button--fs" title="Toggle fullscreen"></button>

                <button class="pswp__button pswp__button--zoom" title="Zoom in/out"></button>

                <!-- Preloader demo http://codepen.io/dimsemenov/pen/yyBWoR -->
                <!-- element will get class pswp__preloader--active when preloader is running -->
                <div class="pswp__preloader">
                    <div class="pswp__preloader__icn">
                        <div class="pswp__preloader__cut">
                            <div class="pswp__preloader__donut"></div>
                        </div>
                    </div>
                </div>
            </div>

            <div class="pswp__share-modal pswp__share-modal--hidden pswp__single-tap">
                <div class="pswp__share-tooltip"></div>
            </div>

            <button class="pswp__button pswp__button--arrow--left" title="Previous (arrow left)">
            </button>

            <button class="pswp__button pswp__button--arrow--right" title="Next (arrow right)">
            </button>

            <div class="pswp__caption">
                <div class="pswp__caption__center"></div>
            </div>

        </div>

    </div>

</div>

<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/photoswipe@4.1.3/dist/photoswipe.min.css">
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/photoswipe@4.1.3/dist/default-skin/default-skin.min.css">
<script src="https://cdn.jsdelivr.net/npm/photoswipe@4.1.3/dist/photoswipe.min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/photoswipe@4.1.3/dist/photoswipe-ui-default.min.js"></script>

<script>
    function viewer_init() {
        let pswpElement = document.querySelectorAll('.pswp')[0];
        let $imgArr = document.querySelectorAll(('.article-entry img:not(.reward-img)'))

        $imgArr.forEach(($em, i) => {
            $em.onclick = () => {
                // slider展开状态
                // todo: 这样不好，后面改成状态
                if (document.querySelector('.left-col.show')) return
                let items = []
                $imgArr.forEach(($em2, i2) => {
                    let img = $em2.getAttribute('data-idx', i2)
                    let src = $em2.getAttribute('data-target') || $em2.getAttribute('src')
                    let title = $em2.getAttribute('alt')
                    // 获得原图尺寸
                    const image = new Image()
                    image.src = src
                    items.push({
                        src: src,
                        w: image.width || $em2.width,
                        h: image.height || $em2.height,
                        title: title
                    })
                })
                var gallery = new PhotoSwipe(pswpElement, PhotoSwipeUI_Default, items, {
                    index: parseInt(i)
                });
                gallery.init()
            }
        })
    }
    viewer_init()
</script> 
<!-- MathJax -->

<!-- Katex -->

<!-- busuanzi  -->
 
<script src="/blog-page/js/busuanzi-2.3.pure.min.js"></script>
 
<!-- ClickLove -->

<!-- ClickBoom1 -->

<!-- ClickBoom2 -->

<!-- CodeCopy -->
 
<link rel="stylesheet" href="/blog-page/css/clipboard.css">
 <script src="https://cdn.jsdelivr.net/npm/clipboard@2/dist/clipboard.min.js"></script>
<script>
  function wait(callback, seconds) {
    var timelag = null;
    timelag = window.setTimeout(callback, seconds);
  }
  !function (e, t, a) {
    var initCopyCode = function(){
      var copyHtml = '';
      copyHtml += '<button class="btn-copy" data-clipboard-snippet="">';
      copyHtml += '<i class="ri-file-copy-2-line"></i><span>COPY</span>';
      copyHtml += '</button>';
      $(".highlight .code pre").before(copyHtml);
      $(".article pre code").before(copyHtml);
      var clipboard = new ClipboardJS('.btn-copy', {
        target: function(trigger) {
          return trigger.nextElementSibling;
        }
      });
      clipboard.on('success', function(e) {
        let $btn = $(e.trigger);
        $btn.addClass('copied');
        let $icon = $($btn.find('i'));
        $icon.removeClass('ri-file-copy-2-line');
        $icon.addClass('ri-checkbox-circle-line');
        let $span = $($btn.find('span'));
        $span[0].innerText = 'COPIED';
        
        wait(function () { // 等待两秒钟后恢复
          $icon.removeClass('ri-checkbox-circle-line');
          $icon.addClass('ri-file-copy-2-line');
          $span[0].innerText = 'COPY';
        }, 2000);
      });
      clipboard.on('error', function(e) {
        e.clearSelection();
        let $btn = $(e.trigger);
        $btn.addClass('copy-failed');
        let $icon = $($btn.find('i'));
        $icon.removeClass('ri-file-copy-2-line');
        $icon.addClass('ri-time-line');
        let $span = $($btn.find('span'));
        $span[0].innerText = 'COPY FAILED';
        
        wait(function () { // 等待两秒钟后恢复
          $icon.removeClass('ri-time-line');
          $icon.addClass('ri-file-copy-2-line');
          $span[0].innerText = 'COPY';
        }, 2000);
      });
    }
    initCopyCode();
  }(window, document);
</script>
 
<!-- CanvasBackground -->
 
<script src="/blog-page/js/dz.js"></script>
 
<script>
  if (window.mermaid) {
    mermaid.initialize({ theme: "forest" });
  }
</script>


    
    

  </div>
</body>

</html>